package swingExtended;

import java.util.TimerTask;
import javax.swing.JProgressBar;

/**
 * A JProgressBar that also estimates the time remaining using the execution
 * time of each task. Note, the estimated time will only be useful if each
 * task takes roughly the same amount of time. 
 * 
 * @author Shimu
 * @date 23-Feb-2013
 */
public class EstimatingProgressBar extends JProgressBar {

    private final time.Timer timer = new time.Timer();

    private java.util.Timer countDownTimer;

    private long remainingTime = 0;

    private long elapsedTime = 0;
    
    private double averageTime = 0;
    
    private double rate = 0;
    
    public EstimatingProgressBar() {
        this.setStringPainted(true);
        this.setMinimum(0);
    }

    public void start() {
        this.setValue(0);
        this.setIndeterminate(true);
        timer.start();
        this.setString("0% unknown time remaining");
        countDownTimer = new java.util.Timer();
        countDownTimer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                if (!isIndeterminate()) {
                    setRemainingTime(Math.max(remainingTime - 1000, 0));
                }
            }
        }, 0, 1000);
    }

    public void finish() {
        this.setValue(this.getMaximum());
        this.setString("100% Done");
        this.setIndeterminate(false);
        countDownTimer.cancel();
        this.repaint();
    }

    /**
     * Convert time from milliseconds to x day y hours z minutes
     * @param ms
     * @return 
     */
    private String getTime(long ms) {
        // 1 Day    = 86400000 ms
        // 1 Hour   =  3600000 ms
        // 1 Minute =    60000 ms
        // 1 Second =     1000 ms
        long remaining = ms;

        long day = remaining / 86400000;
        remaining %= 86400000;

        long hour = remaining / 3600000;
        remaining %= 3600000;

        long minute = remaining / 60000;
        remaining %= 60000;

        long second = remaining / 1000;

        StringBuilder b = new StringBuilder(80);

        if (day > 0) {
            b.append(day);
            b.append(" day ");
        }

        if (hour > 0) {
            b.append(hour);
            b.append(" hour ");
        }

        if (minute > 0) {
            b.append(minute);
            b.append(" minutes ");
        }

        b.append(second);
        b.append(" seconds");

        return b.toString();
    }
    
    public void setValue(double newValue) {
        setValue((int) newValue);
    }

    /**
     * Returns the average time (in ms) to complete a task.
     * @return 
     */
    public double getAverageTime() {
        return this.averageTime;
    }
    
    /**
     * Returns the number of tasks that gets completed in one ms (the
     * inverse of the average time).
     * @return 
     */
    public double getRate() {
        return this.rate;
    }
    
    public void incrementValue(double n) {
        elapsedTime += timer.stop();
        setValue(getValue() + n);
        this.setIndeterminate(false);

        int remainingTasks = this.getMaximum() - this.getValue();

        if (remainingTasks <= 0) {
            this.finish();

        } else {
            // The average time (in milliseconds) it took to complete the 
            // previous task(s)
            if (getValue() == 0) {
                averageTime = 0;       
            } else {
                averageTime = (elapsedTime / (double) getValue());                
            }
            
            if (elapsedTime == 0) {
                rate = Double.MAX_VALUE;                
            } else {
                rate = getValue() / (double) elapsedTime;
            }

            // Use the average time to complete the previous task(s) to estimate
            // the time it takes to complete the remaining task(s)
            setRemainingTime((long) (remainingTasks * averageTime));

            // Recompute the average time it takes to complete each task
            // Multiply by completedTasks to get the total sum of all execution time
            // Add the elapsed time of the new task, then divide by the new number 
            // of completedTasks.
            // averageTime = (averageTime * completedTasks + elapsedTime) /
            //        (completedTasks + 1);
            timer.start();
        }
    }

    private synchronized void setRemainingTime(long ms) {
        this.remainingTime = ms;
        int percent = (int) (this.getPercentComplete() * 100);
        this.setString(String.format("%d%% Estimated %s remaining", percent,
                getTime(remainingTime)));
    }
}
